home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 5
/
Skunkware 5.iso
/
src
/
X11
/
xconq
/
mplay.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-05-09
|
36KB
|
1,307 lines
/* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */
/* This program may be used, copied, modified, and redistributed freely */
/* for noncommercial purposes, so long as this notice remains intact. */
#pragma comment(exestr, "@(#) mplay.c 12.1 95/05/09 ")
/* RCS $Header: mplay.c,v 1.3 88/07/20 16:07:59 shebs Exp $ */
/* This file implements all of machine strategy. Not much room for fancy */
/* tricks, just solid basic play. The code emphasizes avoidance of mistakes */
/* instead of strategic brilliance, so machine behavior is akin to bulldozer */
/* plodding. Nevertheless, bulldozers can be very effective when they */
/* outnumber the human players... */
/* It is also very important to prevent infinite loops, so no action of the */
/* machine player is 100% certain. */
#include "config.h"
#include "misc.h"
#include "dir.h"
#include "period.h"
#include "side.h"
#include "unit.h"
#include "map.h"
#include "global.h"
/* Maximum number of unit groups that can be maintained. Need groups for */
/* both offense and defense. */
#define MAXGROUPS 80
/* the non-group */
#define NOGROUP 0
/* Goals for both groups and individuals. */
#define NOGOAL 0
#define DRIFT 1
#define HITTARGET 2
#define CAPTARGET 3
#define BESIEGE 4
#define OCCUPYHEX 5
#define EXPLORE 6
#define DEFEND 7
#define LOAD 8
#define APPROACH 9
#define RELOAD 10
/* Groups organize machine player activity at the multiple-unit level. */
typedef struct a_group {
short goal; /* the intended purpose of the group */
short priority; /* how important the group is */
short x, y; /* a relevant location */
short etype; /* type of a unit there (or NOTHING) */
short area; /* radius of relevance of group activity */
short member[MAXUTYPES]; /* number of each type in group */
short need[MAXUTYPES]; /* what group wants but doesn't have */
} Group;
/* This structure is where machine sides keep all the plans and planning */
/* related data. */
/* Group 0 is never actually used (a sort of a dummy for various purposes). */
typedef struct a_plan {
short estimate[MAXSIDES][MAXUTYPES]; /* estimated numbers of units */
short allies[MAXSIDES][MAXUTYPES]; /* strength of other alliances */
short cx, cy; /* "centroid" of all our units */
short lastreplan; /* last turn we rechecked the plans */
short demand[MAXUTYPES]; /* worth of each utype w.r.t. strategy */
Group group[MAXGROUPS]; /* all the groups that can be formed */
} Plan;
/* Encapsulate some pointer-chasing and casting messiness. */
#define side_plan(s) ((Plan *) (s)->plan)
/* Malloced integer array accessors and modifers. */
#define aref(m,x,y) ((m)[(x)+world.width*(y)])
#define aset(m,x,y,v) ((m)[(x)+world.width*(y)] = (v))
int evaluate_hex(), maximize_worth();
char groupbuf[BUFSIZE]; /* buffer for group debugging print */
char shortbuf[BUFSIZE]; /* buffer for short unit description */
/* General collections of numbers used by all machine players. */
int fraction[MAXTTYPES]; /* percentages of terrain types in world */
int bhw[MAXUTYPES][MAXUTYPES]; /* basic worth for hitting */
int bcw[MAXUTYPES][MAXUTYPES]; /* basic worth for capturing */
int maxoccupant[MAXUTYPES]; /* total capacity of a transport */
int *localworth; /* for evaluation of nearby hexes */
int bestworth = -10000, bestx, besty;
Unit *munit; /* Unit being decided about */
Side *mside; /* Side whose unit is being decided about */
/* List out data about a group (very compactly). */
char *
group_desig(plan, g)
Plan *plan;
int g;
{
int e = plan->group[g].etype;
sprintf(groupbuf, "group %d goal %d pri %d ->%d,%d %d (%c)",
g, plan->group[g].goal, plan->group[g].priority,
plan->group[g].x, plan->group[g].y, plan->group[g].area,
(e == NOTHING ? ' ' : utypes[e].uchar));
return groupbuf;
}
/* Short unreadable but greppable listing of unit. */
char *
unit_desig(unit)
Unit *unit;
{
sprintf(shortbuf, "s%d %d %c (%d,%d)",
side_number(unit->side), unit->number, utypes[unit->type].uchar,
unit->x, unit->y);
return shortbuf;
}
/* Init used by all machine players. Precompute useful information */
/* relating to unit types in general, and that usually gets referenced */
/* in inner loops. */
init_mplayers()
{
int u, u2, t, g;
Side *side, *side2;
localworth = (int *) malloc(world.width*world.height*sizeof(int));
for_all_terrain_types(t) {
fraction[t] = ((ttypes[t].maxalt - ttypes[t].minalt) *
(ttypes[t].maxwet - ttypes[t].minwet)) / 100;
}
for_all_unit_types(u) {
maxoccupant[u] = 0;
for_all_unit_types(u2) {
bhw[u][u2] = basic_hit_worth(u, u2);
bcw[u][u2] = basic_capture_worth(u, u2);
maxoccupant[u] += utypes[u].capacity[u2];
}
}
/* tell us about how things rated */
if (Debug) {
for_all_terrain_types(t) {
printf("%3d%% ", fraction[t]);
}
printf("\n\n");
for_all_unit_types(u) {
for_all_unit_types(u2) printf("%5d", bhw[u][u2]);
printf("\n");
}
printf("\n");
for_all_unit_types(u) {
for_all_unit_types(u2) printf("%5d", bcw[u][u2]);
printf("\n");
}
printf("\n");
}
/* For all sides, because human might use "robot" option */
for_all_sides(side) {
side->plan = (long) (Plan *) malloc(sizeof(Plan));
side_plan(side)->cx = side_plan(side)->cy = 0;
for (g = 0; g < MAXGROUPS; ++g) {
side_plan(side)->group[g].goal = NOGROUP;
side_plan(side)->group[g].priority = 0;
}
side_plan(side)->lastreplan = -100;
}
}
/* A crude estimate of the payoff of one unit type hitting on another type. */
/* This is just for general estimation, since actual worth may depend on */
/* damage already sustained, unit's goals, etc. */
basic_hit_worth(u, e)
int u, e;
{
int worth, anti;
worth = utypes[u].hit[e] * min(utypes[e].hp, utypes[u].damage[e]);
if (utypes[e].hp > utypes[u].damage[e]) {
worth /= utypes[e].hp;
}
if (period.counterattack) {
anti = utypes[e].hit[u] * min(utypes[u].hp, utypes[e].damage[u]);
if (utypes[u].hp > utypes[e].damage[u]) {
anti /= utypes[u].hp;
}
}
worth -= anti;
return worth;
}
/* A crude estimate of the payoff of one unit type trying to capture. */
basic_capture_worth(u, e)
int u, e;
{
int worth = 0, anti = 0;
if (could_capture(u, e)) {
worth += utypes[u].capture[e] * utypes[u].hp;
}
return worth;
}
/* At the beginning of each turn, can make plans and review the situation. */
/* This should be frequent at first, but rather expensive to do always, */
/* so only some chance of doing it on a random turn. */
init_machine_turn(side)
Side *side;
{
if (global.time < 20 || probability(30)) make_strategy(side);
if (global.time < 20 || probability(50)) review_groups(side);
if (global.time > 20 || probability(20)) decide_resignation(side);
}
/* Strategy is based on a study of the entire world map, looking for */
/* duties and opportunities. */
make_strategy(side)
Side *side;
{
int u, i, g, x, y, view, etype, x0, x1, x2, y1, y2, choice;
int pri, sumx = 0, sumy = 0, n = 0;
Plan *plan = side_plan(side);
Side *side2, *eside;
for_all_sides(side2) {
for_all_unit_types(u) {
plan->estimate[side_number(side2)][u] = 0;
}
}
for (y = 0; y < world.height; ++y) {
for (x = 0; x < world.width; ++x) {
view = side_view(side, x, y);
if (view != EMPTY && view != UNSEEN) {
if (side == side_n(vside(view))) {
sumx += x; sumy += y; n++;
}
}
}
}
if (n > 0) {
plan->cx = sumx / n; plan->cy = sumy / n;
}
for (y = 0; y < world.height; ++y) {
for (x = 0; x < world.width; ++x) {
view = side_view(side, x, y);
if (view != EMPTY && view != UNSEEN) {
eside = side_n(vside(view));
etype = vtype(view);
if (!allied_side(side, eside)) {
choice = attack_type(etype);
if (eside == NULL && choice == HITTARGET) {
/* uncapturable neutrals are basically dull */
} else if (!find_group(side, choice, x, y)) {
pri = 100 - (100 * distance(x, y, plan->cx, plan->cy))
/ world.width;
form_group(side, choice, pri+1, x, y, 0, etype);
}
} else {
if (!mobile(etype) && !defended(side, x, y)) {
form_group(side, DEFEND, 1, x, y, 3, NOTHING);
}
}
if (eside) plan->estimate[side_number(eside)][etype]++;
}
}
}
if (!world.known) {
if (!find_group(side, EXPLORE, -1, -1)) {
x0 = 0 + random(world.width/3);
x1 = world.width/3 + random(world.width/3);
x2 = (2*world.width)/3 + random(world.width/3);
y1 = 0 + random(world.height/3);
y2 = (2*world.height)/3 + random(world.height/3);
form_group(side, EXPLORE, 1, x0, y1, 3, NOTHING);
form_group(side, EXPLORE, 1, x1, y1, 3, NOTHING);
form_group(side, EXPLORE, 1, x2, y1, 3, NOTHING);
form_group(side, EXPLORE, 1, x0, y2, 3, NOTHING);
form_group(side, EXPLORE, 1, x1, y2, 3, NOTHING);
form_group(side, EXPLORE, 1, x2, y2, 3, NOTHING);
}
}
/* should form a hex occupation group if hex mentioned in win/lose */
}
/* Decides if unit has nothing covering it. */
defended(side, x, y)
Side *side;
int x, y;
{
int g;
Plan *plan = side_plan(side);
for (g = 1; g < MAXGROUPS; ++g) {
if ((plan->group[g].goal == DEFEND) &&
(distance(x, y, plan->group[g].x, plan->group[g].y) <=
plan->group[g].area))
return TRUE;
}
return FALSE;
}
/* Review existing groups and get rid of useless ones. Start by recomputing */
/* the members, since we don't update when units die or get transferred. */
/* (Since at beginning of turn, all units known to be alive.) */
review_groups(side)
Side *side;
{
int g, u, u2, e, view, ideal;
Plan *plan = side_plan(side);
Group *group;
Unit *unit;
for_all_unit_types(u) {
plan->demand[u] = 0;
}
for (g = 1; g < MAXGROUPS; ++g) {
for_all_unit_types(u) {
plan->group[g].member[u] = 0;
plan->group[g].need[u] = 0;
}
}
for_all_units(unit) {
if (unit->side == side) plan->group[unit->group].member[unit->type]++;
}
for (g = 1; g < MAXGROUPS; ++g) {
group = &(plan->group[g]);
switch (group->goal) {
case NOGROUP:
/* a non-existent group */
break;
case HITTARGET:
view = side_view(side, group->x, group->y);
if (view == EMPTY || view == UNSEEN ||
side_n(vside(view)) == NULL ||
allied_side(side, side_n(vside(view)))) {
disband_group(side, g);
} else {
for_all_unit_types(u) {
if (could_hit(u, group->etype)) {
e = group->etype;
if (utypes[u].damage[e] > 0) {
ideal = (2 * utypes[e].hp * utypes[u].hit[e]) /
utypes[u].damage[e];
} else {
ideal = 0;
}
group->need[u] = ideal - group->member[u];
for_all_unit_types(u2) {
if (could_carry(u2, u)) {
ideal = 1;
group->need[u2] = ideal - group->member[u2];
}
}
}
}
}
break;
case CAPTARGET:
view = side_view(side, group->x, group->y);
if (view == EMPTY || view == UNSEEN ||
allied_side(side, side_n(vside(view)))) {
disband_group(side, g);
} else {
for_all_unit_types(u) {
if (could_capture(u, group->etype)) {
ideal = 200 / utypes[u].capture[group->etype];
group->need[u] = ideal - group->member[u];
for_all_unit_types(u2) {
if (could_carry(u2, u)) {
ideal = 1;
group->need[u2] = ideal - group->member[u2];
}
}
}
}
}
break;
case EXPLORE:
view = side_view(side, group->x, group->y);
if (view != UNSEEN) {
disband_group(side, g);
} else {
for_all_unit_types(u) {
if (mobile(u)) ideal = 3;
group->need[u] = ideal - group->member[u];
for_all_unit_types(u2) {
if (could_carry(u2, u)) {
ideal = 1;
group->need[u2] = ideal - group->member[u2];
}
}
}
}
break;
case DEFEND:
for_all_unit_types(u) {
ideal = 3;
group->need[u] = ideal - group->member[u];
}
break;
case OCCUPYHEX:
/* occupying should only end if no longer a victory condition */
break;
default:
case_panic("group goal", group->goal);
break;
}
}
for (g = 1; g < MAXGROUPS; ++g) {
for_all_unit_types(u) {
plan->demand[u] = group->priority * group->need[u];
}
}
}
/* Sometimes there is no point in going on, but be careful not to be too */
/* pessimistic. Right now we only give up if no hope at all. */
decide_resignation(side)
Side *side;
{
int u, u2, sn1, inrunning = FALSE, opposed, own, odds, chance = 0;
Side *side1, *side2;
Plan *plan = side_plan(side);
for_all_sides(side1) {
sn1 = side_number(side1);
for_all_unit_types(u) {
plan->allies[sn1][u] = plan->estimate[sn1][u];
for_all_sides(side2) {
if (side1 != side2 && allied_side(side1, side2)) {
plan->allies[sn1][u] +=
plan->estimate[side_number(side2)][u];
}
}
}
}
if (global.numconds == 0) {
for_all_unit_types(u) {
own = plan->allies[side_number(side)][u];
for_all_unit_types(u2) {
if (could_make(u, u2) && mobile(u2))
inrunning = TRUE;
for_all_sides(side1) {
if (enemy_side(side, side1)) {
opposed = plan->allies[side_number(side1)][u2];
if (own > 0 && opposed > 0) {
if (could_capture(u, u2) && mobile(u))
inrunning = TRUE;
if (could_hit(u, u2) && mobile(u))
inrunning = TRUE;
}
}
}
}
}
/* should use chance for doubtful situations, like relative strength */
if (!inrunning || probability(chance)) resign_game(side, NULL);
} else {
/* could get pretty complicated... */
}
}
/* When forming a group, first pick out an unused group, then bump a lower */
/* priority group if there's too many. If it's of lower or equal priority, */
/* then don't form the group at all (failure on equal priorities reduces */
/* fickleness). */
form_group(side, goal, priority, x, y, area, etype)
Side *side;
int goal, priority, x, y, area, etype;
{
int g, u;
Plan *plan = side_plan(side);
for (g = 1; g < MAXGROUPS; ++g) {
if (plan->group[g].goal == NOGROUP) break;
}
if (g == MAXGROUPS) {
for (g = 1; g < MAXGROUPS; ++g) {
if (priority > plan->group[g].priority) {
disband_group(side, g);
break;
}
}
}
if (g < MAXGROUPS) {
plan->group[g].goal = goal;
plan->group[g].priority = priority;
plan->group[g].x = x;
plan->group[g].y = y;
plan->group[g].area = area;
plan->group[g].etype = etype;
for_all_unit_types(u) {
plan->group[g].member[u] = 0;
plan->group[g].need[u] = 0;
}
if (Debug) printf("%d: s%d form %s\n", global.time,
side_number(side), group_desig(plan, g));
return g;
} else {
return 0;
}
}
/* When group's goal accomplished, release the units for other activities. */
/* Not very efficient to scan all units, but simpler and safer than links. */
/* (All units are known to be alive here.) */
disband_group(side, g)
Side *side;
int g;
{
Unit *unit;
Plan *plan = side_plan(side);
if (Debug) printf("%d: s%d disband %s\n", global.time,
side_number(side), group_desig(plan, g));
plan->group[g].goal = NOGROUP;
plan->group[g].priority = 0;
for_all_units(unit) {
if (unit->side == side && unit->group == g) {
unit->group = NOGROUP;
unit->goal = NOGOAL;
if (Debug) printf("%d: %s released from group %d\n",
global.time, unit_desig(unit), g);
}
}
}
/* Given a goal and argument, see if a group already exists like that. */
/* (-1 values serve as unbound variables.) */
find_group(side, goal, x, y)
Side *side;
int goal, x, y;
{
int g;
for (g = 1; g < MAXGROUPS; ++g) {
if ((side_plan(side)->group[g].goal == goal) &&
(x == -1 || side_plan(side)->group[g].x == x) &&
(y == -1 || side_plan(side)->group[g].y == y))
return g;
}
return 0;
}
/* Decide whether a change of product is desirable. */
change_machine_product(unit)
Unit *unit;
{
int u = unit->type;
if (Freeze) {
return FALSE;
} else if (utypes[u].maker) {
if (producing(unit)) {
if ((unit->built > 5) ||
((utypes[u].make[unit->product] * unit->built) > 50)) {
return TRUE;
}
} else {
return TRUE;
}
}
return FALSE;
}
/* Machine algorithm for deciding what a unit should build. This routine */
/* must return the type of unit decided upon. Variety of production is */
/* important, as is favoring types which can leave the builder other than */
/* on a transport. Capturers of valuable units are also highly preferable. */
machine_product(unit)
Unit *unit;
{
int u = unit->type, u2, type, t, d, x, y, value, bestvalue, besttype, tmp;
int adjterr[MAXTTYPES];
mside = unit->side;
for_all_terrain_types(t) adjterr[t] = 0;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = unit->y + diry[d];
adjterr[terrain_at(x, y)]++;
}
besttype = period.firstptype;
bestvalue = 0;
tmp = FALSE;
for_all_unit_types(u2) {
if (could_make(u, u2)) {
value = side_plan(mside)->demand[u2];
if (mobile(u2)) {
for_all_terrain_types(t) {
if (could_move(u2, t)) {
value += adjterr[t] * fraction[t];
tmp = TRUE;
}
}
}
if (mside->building[u2] > 0) value /= (mside->building[u2] + 1);
/* might want to adjust by number of existing units? */
value = (value * (100 - build_time(unit, u2))) / 100;
if (tmp && value > bestvalue) {
besttype = u2;
bestvalue = value;
}
}
}
type = besttype;
/* safety check */
if (!could_make(unit->type, type)) type = NOTHING;
if (Debug) printf("%d: %s will now build %s units\n",
global.time, unit_desig(unit),
(type == NOTHING ? "no" : utypes[type].name));
return type;
}
/* Decide on and make a move or set orders for a machine player. */
machine_move(unit)
Unit *unit;
{
munit = unit;
mside = unit->side;
if (Freeze) {
order_sentry(unit, 1);
} else if (humanside(mside)) {
unit->goal = DRIFT;
if (maybe_return_home(unit)) return;
if (probability(50) && short_term(unit)) return;
search_for_best_move(unit);
} else {
if (unit->group == NOGROUP) decide_group(unit);
if (unit->goal == NOGOAL) decide_goal(unit);
if (maybe_return_home(unit)) return;
if (probability(50) && short_term(unit)) return;
search_for_best_move(unit);
}
}
/* Picking the correct units for a group is essential to its success. */
/* We rate the unit for its suitability for each group based on the needs */
/* of the group and the capabilities and proximity of the unit. */
decide_group(unit)
Unit *unit;
{
int g, u = unit->type, t, suitability, best = 0, bestgroup = 0, dist;
Plan *plan = side_plan(unit->side);
for (g = 1; g < MAXGROUPS; ++g) {
suitability = max(0, plan->group[g].need[u]) * plan->group[g].priority;
switch (plan->group[g].goal) {
case NOGROUP:
break;
case HITTARGET:
case CAPTARGET:
dist = distance(unit->x, unit->y,
plan->group[g].x, plan->group[g].y);
suitability -= (suitability * dist) / world.width;
break;
case EXPLORE:
suitability = 1;
if (!mobile(unit->type)) suitability = -100;
break;
case DEFEND:
suitability = 1;
break;
case OCCUPYHEX:
/* assign a group capable of reaching the hex */
break;
default:
case_panic("group goal", plan->group[g].goal);
break;
}
if (suitability > best) {
best = suitability;
bestgroup = g;
}
}
unit->group = bestgroup;
unit->goal = NOGOAL;
plan->group[bestgroup].member[unit->type]++;
if (Debug) printf("%d: %s assigned to %s\n", global.time,
unit_desig(unit), group_desig(plan, bestgroup));
}
/* Set up goals for units that need them. */
/* Goals should differ according to unit's role in group... */
decide_goal(unit)
Unit *unit;
{
int x, y, area;
Plan *plan = side_plan(unit->side);
x = plan->group[unit->group].x; y = plan->group[unit->group].y;
switch (plan->group[unit->group].goal) {
case NOGOAL:
/* dubious */
unit->goal = DRIFT;
unit->gx = unit->gy = 0;
break;
case HITTARGET:
if (could_hit(unit->type, plan->group[unit->group].etype)) {
unit->goal = HITTARGET;
} else if (probability(fullness(unit))) {
unit->goal = APPROACH;
} else {
unit->goal = LOAD;
}
unit->gx = x; unit->gy = y;
break;
case CAPTARGET:
if (could_capture(unit->type, plan->group[unit->group].etype)) {
unit->goal = CAPTARGET;
} else if (probability(fullness(unit))) {
unit->goal = APPROACH;
} else {
unit->goal = LOAD;
}
unit->gx = x; unit->gy = y;
break;
case EXPLORE:
unit->goal = APPROACH;
area = plan->group[unit->group].area;
unit->gx = x + random(2*area) - area;
unit->gy = y + random(2*area) - area;
break;
case DEFEND:
unit->goal = DRIFT;
area = plan->group[unit->group].area;
unit->gx = x + random(2*area) - area;
unit->gy = y + random(2*area) - area;
break;
case OCCUPYHEX:
unit->goal = APPROACH;
unit->gx = x; unit->gy = y;
break;
default:
case_panic("group goal", plan->group[unit->group].goal);
break;
}
if (Debug) printf("%d: %s in %s gets goal %d->%d,%d\n", global.time,
unit_desig(unit), group_desig(plan, unit->group),
unit->goal, unit->gx, unit->gy);
}
/* See if the location has a unit that can take us in for refueling */
/* (where's the check for refueling ability?) */
haven_p(x, y)
int x, y;
{
Unit *unit = unit_at(x, y);
return ((unit != NULL && mside == unit->side && alive(unit) &&
can_carry(unit, munit) && !might_be_captured(unit)));
}
/* See if the location has a unit that can repair us */
shop_p(x, y)
int x, y;
{
Unit *unit = unit_at(x, y);
return (unit != NULL && munit->side == unit->side && alive(unit) &&
can_carry(unit, munit) && could_repair(unit->type, munit->type) &&
!might_be_captured(unit));
}
/* See if we're in a bad way, either on supply or hits, and get to safety */
/* if possible. If not, then move on to other actions. */
/* Can't be 100% though, there might be some problem preventing move */
maybe_return_home(unit)
Unit *unit;
{
int u = unit->type, ux = unit->x, uy = unit->y, ox, oy, range, success;
if (low_supplies(unit) && probability(98)) {
range = range_left(unit);
if (Debug) printf("%s should get supplies - ", unit_desig(unit));
if ((range * range < numunits) ?
(search_area(ux, uy, range, haven_p, &ox, &oy)) :
(find_closest_unit(ux, uy, range, haven_p, &ox, &oy))) {
order_moveto(unit, ox, oy);
unit->orders.flags |= SHORTESTPATH;
unit->orders.flags &=
~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
if (Debug) printf("will resupply at %d,%d\n", ox, oy);
return TRUE;
} else {
if (Debug) printf("but can't\n");
}
}
if (cripple(unit) && probability(98)) {
/* note that crippled units cannot repair themselves */
if (Debug) printf("%s badly damaged - ", unit_desig(unit));
if (unit->transport && could_repair(u, unit->transport->type)) {
if (Debug) printf("%s will repair\n", unit_desig(unit->transport));
order_sentry(unit, 1);
return TRUE;
} else {
range = range_left(unit);
if ((range * range < numunits) ?
(search_area(ux, uy, range, haven_p, &ox, &oy)) :
(find_closest_unit(ux, uy, range, shop_p, &ox, &oy))) {
order_moveto(unit, ox, oy);
unit->orders.flags &= ~SHORTESTPATH;
unit->orders.flags &=
~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
if (Debug) printf("will repair at %d,%d\n", ox, oy);
return TRUE;
} else {
if (Debug) printf("but no place to repair\n");
}
}
}
if (out_of_ammo(unit) >= 0 && probability(80)) {
if (Debug) printf("%s should reload - ", unit_desig(unit));
range = range_left(unit);
if ((range * range < numunits) ?
(search_area(ux, uy, range, haven_p, &ox, &oy)) :
(find_closest_unit(ux, uy, range, haven_p, &ox, &oy))) {
order_moveto(unit, ox, oy);
unit->orders.flags &= ~SHORTESTPATH;
unit->orders.flags &=
~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
if (Debug) printf("will go to %d,%d\n", ox, oy);
return TRUE;
} else {
if (Debug) printf("but can't\n");
}
}
return FALSE;
}
/* Return the distance that we can go by shortest path before running out */
/* of important supplies. Will return at least 1, since we can *always* */
/* move one hex to safety. This is a worst-case routine, too complicated */
/* to worry about units getting refreshed by terrain or whatever. */
range_left(unit)
Unit *unit;
{
int u = unit->type, r, least = 12345;
for_all_resource_types(r) {
if (utypes[u].tomove[r] > 0) least = min(least, unit->supply[r]);
if (utypes[u].consume[r] > 0)
least = min(least, unit->supply[r] / utypes[u].consume[r]);
}
return (least == 12345 ? 1 : least);
}
/* Do short-range planning. Only thing here is intended to be for defenders */
/* protecting a small area (5 moves is arb, should derive from defense */
/* group area). */
short_term(unit)
Unit *unit;
{
int u = unit->type, ux = unit->x, uy = unit->y, range;
switch (unit->goal) {
case DRIFT:
range = min(10, 5 * utypes[u].speed);
if (probability(90)) {
bestworth = -10000;
apply_to_area(ux, uy, range, evaluate_hex);
apply_to_area(ux, uy, range, maximize_worth);
if (bestworth >= 0) {
if (Debug) printf("drifting to %d,%d (worth %d)\n",
bestx, besty, bestworth);
order_moveto(unit, bestx, besty);
unit->orders.flags &= ~SHORTESTPATH;
return TRUE;
}
}
break;
case LOAD:
case APPROACH:
case HITTARGET:
case CAPTARGET:
break;
default:
case_panic("unit goal", munit->goal);
break;
}
return FALSE;
}
/* Search for most favorable odds anywhere in the area, but only for */
/* the remaining moves in this turn. Multi-turn tactics is elsewhere. */
search_for_best_move(unit)
Unit *unit;
{
int ux = unit->x, uy = unit->y, range = unit->movesleft, goal;
if (!mobile(unit->type)) {
order_sentry(unit, 100);
return;
}
if (Debug) printf("%d: %s ", global.time, unit_desig(unit));
bestworth = -10000;
apply_to_area(ux, uy, range, evaluate_hex);
apply_to_area(ux, uy, range, maximize_worth);
if (bestworth >= 0) {
if (unit->transport != NULL && mobile(unit->transport->type)) {
if (Debug) printf("sleeping on transport\n");
order_sentry(unit, 5);
} else if ((ux == bestx && uy == besty) || !can_move(unit)) {
if (Debug) printf("staying put\n");
order_sentry(unit, 1);
} else if (probability(90)) {
if (Debug) printf("moving to %d,%d (worth %d)\n",
bestx, besty, bestworth);
order_moveto(unit, bestx, besty);
unit->orders.flags &= ~SHORTESTPATH;
} else {
if (Debug) printf("hanging around\n");
order_sentry(unit, random(5));
}
} else {
goal = unit->goal;
/* jam alternative sometimes... */
if (probability(95)) goal = DRIFT;
switch (goal) {
case DRIFT:
if (can_produce(unit) && unit->transport == NULL &&
probability(90)) {
if (Debug) printf("going to build something\n");
set_product(unit, machine_product(unit));
set_schedule(unit);
order_sentry(unit, unit->schedule+1);
} else if (probability(90)) {
if (Debug) printf("going in random direction\n");
order_movedir(unit, random_dir(), random(3)+1);
} else {
if (Debug) printf("hanging around\n");
order_sentry(unit, random(4)+1);
}
break;
case LOAD:
if (unit->occupant != NULL) {
if (Debug) printf("starting off to goal\n");
unit->goal = APPROACH;
order_moveto(unit, unit->gx, unit->gy);
} else {
if (bestworth >= 0) {
if (Debug) printf("loading at %d,%d (worth %d)\n",
bestworth, bestx, besty);
order_moveto(unit, bestx, besty);
unit->orders.flags &= ~SHORTESTPATH;
} else {
if (Debug) printf("moving slowly about\n");
order_movedir(unit, random_dir(), 1);
}
}
break;
case APPROACH:
case HITTARGET:
case CAPTARGET:
if (unit->transport != NULL) {
if (unit->transport->group == unit->group) {
if (Debug) printf("riding in transport\n");
order_sentry(unit, 4);
} else if (!can_move(unit)) {
if (Debug) printf("waiting to get off\n");
order_sentry(unit, 2);
} else {
if (Debug) printf("leaving for %d,%d\n",
unit->gx, unit->gy);
order_moveto(unit, unit->gx, unit->gy);
}
} else {
if (Debug) printf("approaching %d,%d\n", unit->gx, unit->gy);
order_moveto(unit, unit->gx, unit->gy);
}
break;
default:
case_panic("unit goal", munit->goal);
break;
}
}
}
/* Given a position nearby the unit, evaluate it with respect to goals, */
/* general characteristics, and so forth. -10000 is very bad, 0 is OK, */
/* 10000 or so is best possible. */
/* Should downrate hexes within reach of enemy retaliation. */
/* Should downrate hexes requiring supply consumption to enter/occupy. */
evaluate_hex(x, y)
int x, y;
{
bool adjhex, ownhex;
int view, etype, dist, worth = 0;
int terr = terrain_at(x, y);
Side *es;
Unit *eunit;
view = side_view(mside, x, y);
dist = distance(munit->x, munit->y, x, y);
adjhex = (dist == 1);
ownhex = (dist == 0);
if (y <= 0 || y >= world.height-1) {
worth = -10000;
} else {
switch (munit->goal) {
case DRIFT:
if (ownhex) {
worth = -1;
} else if (view == UNSEEN) {
worth = random(100) / dist;
} else if (view == EMPTY) {
worth = -100;
if (impassable(munit, x, y)) worth -= 900;
} else {
es = side_n(vside(view));
etype = vtype(view);
if (es == NULL) {
if (could_capture(munit->type, etype)) {
worth = 20000 / dist;
} else {
worth = -10000;
}
} else if (!allied_side(mside, es)) {
worth = 200 + attack_worth(munit, etype);
worth += threat(mside, etype, x, y);
worth /= dist;
} else {
worth = 0;
}
}
break;
case LOAD:
if (ownhex || view == UNSEEN || view == EMPTY) {
worth = -1;
} else {
es = side_n(vside(view));
if (mside == es) {
if ((eunit = unit_at(x, y)) != NULL) {
if (eunit->group == munit->group) {
worth = 4000;
worth /= dist;
}
}
} else {
worth = -100;
}
}
break;
case APPROACH:
case HITTARGET:
case CAPTARGET:
if (ownhex) {
worth = -100;
} else if (view == UNSEEN) {
worth = random(100) / dist;
} else if (view == EMPTY) {
if (impassable(munit, x, y)) worth -= 900;
} else if (x == munit->gx && y == munit->gy) {
worth = 10000;
} else {
es = side_n(vside(view));
etype = vtype(view);
if (es == NULL) {
if (could_capture(munit->type, etype)) {
worth = 20000 / dist;
} else {
worth = -10000;
}
} else if (!allied_side(mside, es)) {
worth = 200 + attack_worth(munit, etype);
worth += threat(mside, etype, x, y);
worth /= dist;
} else {
es = side_n(vside(view));
if (mside == es) {
if ((eunit = unit_at(x, y)) != NULL) {
if (eunit->group == munit->group &&
eunit->goal == LOAD &&
could_carry(eunit->type, munit->type)) {
worth = 4000;
worth /= dist;
}
}
} else {
worth = -100;
}
}
}
break;
default:
case_panic("unit goal", munit->goal);
break;
}
}
if ((munit->gx > 0 || munit->gy > 0) &&
(distance(x, y, munit->gx, munit->gy) <
distance(munit->x, munit->y, munit->gx, munit->gy))) {
worth += 1000;
}
worth -= 100;
worth += utypes[munit->type].productivity[terr];
aset(localworth, x, y, worth);
}
/* Scan evaluated area looking for best overall hex. */
maximize_worth(x, y)
int x, y;
{
int worth;
worth = aref(localworth, x, y);
if (worth >= 0) {
if (worth > bestworth) {
bestworth = worth; bestx = x; besty = y;
} else if (worth == bestworth && flip_coin()) {
bestworth = worth; bestx = x; besty = y;
}
}
}
/* This is a heuristic estimation of the value of one unit type hitting */
/* on another. Should take cost of production into account as well as the */
/* chance and significance of any effect. */
attack_worth(unit, etype)
Unit *unit;
int etype;
{
int utype = unit->type, worth;
worth = bhw[utype][etype];
if (utypes[utype].damage[etype] >= utypes[etype].hp)
worth *= 2;
if (utypes[etype].damage[utype] >= unit->hp)
worth /= (could_capture(utype, etype) ? 1 : 4);
if (could_capture(utype, etype)) worth *= 4;
return worth;
}
/* Support functions. */
/* True if unit is in immediate danger of being captured. */
/* Needs check on capturer transport being seen. */
might_be_captured(unit)
Unit *unit;
{
int d, x, y;
Unit *unit2;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = unit->y + diry[d];
if (((unit2 = unit_at(x, y)) != NULL) &&
(enemy_side(unit->side, unit2->side)) &&
(could_capture(unit2->type, unit->type))) return TRUE;
}
return FALSE;
}
/* Return true if the given unit type at given position is threatened. */
threat(side, u, x0, y0)
Side *side;
int u, x0, y0;
{
int d, x, y, view, thr = 0;
Side *side2;
for_all_directions(d) {
x = wrap(x0 + dirx[d]); y = y0 + diry[d];
view = side_view(side, x, y);
if (view != UNSEEN && view != EMPTY) {
side2 = side_n(vside(view));
if (allied_side(side, side2)) {
if (could_capture(u, vtype(view))) thr += 1000;
if (bhw[u][vtype(view)] > 0) thr += 100;
}
}
}
return thr;
}
/* Test if unit can move out into adjacent hexes. */
can_move(unit)
Unit *unit;
{
int d, x, y;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = limit(unit->y + diry[d]);
if (could_move(unit->type, terrain_at(x, y))) return TRUE;
}
return FALSE;
}
/* Returns the type of missing supplies. */
out_of_ammo(unit)
Unit *unit;
{
int u = unit->type, r;
for_all_resource_types(r) {
if (utypes[u].hitswith[r] > 0 && unit->supply[r] <= 0)
return r;
}
return (-1);
}
/* Returns the type of attack to plan for. (Should balance relative */
/* effectiveness of each type of attack.) */
attack_type(e)
int e;
{
int u;
if (utypes[e].surrender > 0 || utypes[e].siege > 0) return BESIEGE;
for_all_unit_types(u) if (could_capture(u, e)) return CAPTARGET;
return HITTARGET;
}
/* True if the given unit is a sort that can build other units. */
can_produce(unit)
Unit *unit;
{
int p;
for_all_unit_types(p) {
if (could_make(unit->type, p)) return TRUE;
}
return FALSE;
}
/* Return percentage of capacity. */
fullness(unit)
Unit *unit;
{
int u = unit->type, o, cap = 0, num = 0, vol = 0;
Unit *occ;
for_all_unit_types(o) cap += utypes[u].capacity[o];
for_all_occupants(unit, occ) {
num++;
vol += utypes[occ->type].volume;
}
if (utypes[u].holdvolume > 0) {
return ((100 * vol) / utypes[u].holdvolume);
} else if (cap > 0) {
return ((100 * num) / cap);
} else {
fprintf(stderr, "Fullness ???\n");
}
}
find_closest_unit(x0, y0, maxdist, pred, rxp, ryp)
int x0, y0, maxdist, (*pred)(), *rxp, *ryp;
{
Unit *unit;
for_all_units(unit) {
if (alive(unit) && distance(x0, y0, unit->x, unit->y) <= maxdist) {
if ((*pred)(unit->x, unit->y)) {
*rxp = unit->x; *ryp = unit->y;
return TRUE;
}
}
}
return FALSE;
}